
#include "spdlog/stopwatch.h"
#include "levenshtein.hpp"
// #include "Yolov5.h"
// #include "landmark.h"
// #include "crnn.h"
// #include "lpr_infer_api.h"

static inline bool transform2d(const float *mean, const float *landmark, int N, float *M)
{
	const float *std_points = landmark;
	const float *src_points = mean;

	float sum_x = 0, sum_y = 0;
	float sum_u = 0, sum_v = 0;
	float sum_xx_yy = 0;
	float sum_ux_vy = 0;
	float sum_vx_uy = 0;

	for (int c = 0; c < N; ++c) {
		int x_off = c * 2;
		int y_off = x_off + 1;
		sum_x += std_points[c * 2];
		sum_y += std_points[c * 2 + 1];
		sum_u += src_points[x_off];
		sum_v += src_points[y_off];
		sum_xx_yy += std_points[c * 2] * std_points[c * 2] + std_points[c * 2 + 1] * std_points[c * 2 + 1];
		sum_ux_vy += std_points[c * 2] * src_points[x_off] + std_points[c * 2 + 1] * src_points[y_off];
		sum_vx_uy += src_points[y_off] * std_points[c * 2] - src_points[x_off] * std_points[c * 2 + 1];
	}

	if (sum_xx_yy <= FLT_EPSILON)
		return false;

	float q = sum_u - sum_x * sum_ux_vy / sum_xx_yy + sum_y * sum_vx_uy / sum_xx_yy;
        float p = sum_v - sum_y * sum_ux_vy / sum_xx_yy - sum_x * sum_vx_uy / sum_xx_yy;
	float r = N - (sum_x * sum_x + sum_y * sum_y) / sum_xx_yy;

	if (!(r > FLT_EPSILON || r < -FLT_EPSILON))
		return false;

	float a = (sum_ux_vy - sum_x * q / r - sum_y * p / r) / sum_xx_yy;
	float b = (sum_vx_uy + sum_y * q / r - sum_x * p / r) / sum_xx_yy;
	float c = q / r;
	float d = p / r;

	M[0] = M[4] = a;
	M[1] = -b;
	M[3] = b;
	M[2] = c;
	M[5] = d;

	return true;
}

void test_sqlite3()
{
    std::cout << "SQlite3 version " << SQLite::VERSION << " (" << SQLite::getLibVersion() << ")" << std::endl;
    std::cout << "SQliteC++ version " << SQLITECPP_VERSION << std::endl;    
    try
    {
        // Open a database file in create/write mode
        SQLite::Database    db("test.db3", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE);
        std::cout << "SQLite database file '" << db.getFilename().c_str() << "' opened successfully\n";

        // Create a new table with an explicit "id" column aliasing the underlying rowid
        db.exec("DROP TABLE IF EXISTS test");
        db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)");

        // first row
        int nb = db.exec("INSERT INTO test VALUES (NULL, \"test\")");
        std::cout << "INSERT INTO test VALUES (NULL, \"test\")\", returned " << nb << std::endl;

        // second row
        nb = db.exec("INSERT INTO test VALUES (NULL, \"second\")");
        std::cout << "INSERT INTO test VALUES (NULL, \"second\")\", returned " << nb << std::endl;

        // update the second row
        nb = db.exec("UPDATE test SET value=\"second-updated\" WHERE id='2'");
        std::cout << "UPDATE test SET value=\"second-updated\" WHERE id='2', returned " << nb << std::endl;

        nb = db.getTotalChanges();
        std::cout << "Nb of total changes since connection: " << nb << std::endl;

        // Check the results : expect two row of result
        SQLite::Statement   query(db, "SELECT * FROM test");
        std::cout << "SELECT * FROM test :\n";
        while (query.executeStep())
        {
            std::cout << "row (" << query.getColumn(0) << ", \"" << query.getColumn(1) << "\")\n";
        }

        db.exec("DROP TABLE test");
    }
    catch (std::exception& e)
    {
        std::cout << "SQLite exception: " << e.what() << std::endl;
        return; // unexpected error : exit the example program
    }
    remove("test.db3");  
}

void test_spdlog()
{
    spdlog::info("Welcome to spdlog!");
    spdlog::error("Some error message with arg: {}", 1);
    
    spdlog::warn("Easy padding in numbers like {:08d}", 12);
    spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
    spdlog::info("Support for floats {:03.2f}", 1.23456);
    spdlog::info("Positional args are {1} {0}..", "too", "supported");
    spdlog::info("{:<30}", "left aligned");
    
    spdlog::set_level(spdlog::level::debug); // Set global log level to debug
    spdlog::debug("This message should be displayed..");
    
    // change log pattern
    spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%n] [%^%l%$] [thread %t] %v");

    spdlog::info("Welcome to spdlog!");
    spdlog::error("Some error message with arg: {}", 1);
    
    spdlog::warn("Easy padding in numbers like {:08d}", 12);
    spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
    spdlog::info("Support for floats {:03.2f}", 1.23456);
    spdlog::info("Positional args are {1} {0}..", "too", "supported");
    spdlog::info("{:<30}", "left aligned");
}

bool test_download_patch()
{
    httplib::Client *m_client = new httplib::Client("unix:/tmp/http_proxy_connect.socket");
    m_client->set_default_headers({ { "Host", "192.168.2.190:9001" }, { "Proxy-Connection", "keep-alive" }, { "Accept-Encoding", "gzip, deflate" } });
    m_client->set_read_timeout(10);
    m_client->set_write_timeout(10);
    m_client->set_keep_alive(true);
    m_client->set_compress(true);
    m_client->set_logger([](const httplib::Request& req, const httplib::Response& res) {
        LOG_INFO("http request: method=%s, %s:%d%s", req.method.c_str(), req.remote_addr.c_str(), req.remote_port, req.path.c_str());
        // LOG_DEBUG("http request: method=%s, path=%s, body=%s", req.method.c_str(), req.path.c_str(), req.body.c_str());
        LOG_INFO("http response: status=%d, body=%s", res.status, res.body.c_str());
    });

    std::ofstream ofs{"./bin/lpdapp_new"};
    auto res = m_client->Get("/data/algv/lpdapp",
        [&](const char *data, size_t dataLen) {
            ofs.write(data, dataLen);
            return true;
    });
    ofs.flush();
    ofs.close();

    LOG_DEBUG("chmod lpdapp_new result:%d", std::system("chmod 755 ./bin/lpdapp_new"));
    LOG_DEBUG("cp lpdapp_new result:%d", std::system("cp -f ./bin/lpdapp_new ./bin/lpdapp"));

    delete m_client;

    return true;
}

bool test_levenshtein()
{
    std::string s0 = "鲁L723R5";
    std::string s1 = "鲁L720R5警";
    std::wstring ws0(s0.begin(), s0.end());
    std::wstring ws1(s1.begin(), s1.end());
    double similarity = levenshtein::GetInstance().wsimilarity(ws0, ws1);

    LOG_INFO("levenshtein of s0=%s and s1=%s is %.2f", s0.c_str(), s1.c_str(), similarity);

    return true;
}

/*
static const float transform2d_mean_100x32[8] = {
	92, 30,
	8,  30,
	8,  2,
	92, 2,
};

static inline bool transform2d_100x32(VIPLPoint *p, float *M)
{
	float landmark[8];
	for (size_t i = 0; i < 4; ++i) {
		landmark[i*2] = p[i].x;
		landmark[i*2 + 1] = p[i].y;
	}
	return transform2d(transform2d_mean_100x32, landmark, 4, M);
}
*/


/*
bool test_ncnn_plate()
{
    spdlog::stopwatch sw;

	Yolov5 yolov5;
	landmark pfld;
	PlateCrnn crnn;

	cv::Mat m = cv::imread("./test/p1.jpg", cv::IMREAD_COLOR);
	if (m.empty()) {
		fprintf(stderr, "cv::imread failed\n");
		return false;
	}
	VIPLObjectInfo objs[100];
	int n = yolov5.detect(&m, objs, 100);

    spdlog::debug("Elapsed for yolo {:.3}", sw);

	for(int i=0; i<n; i++) {
		if (objs[i].label != 0)
			continue;

		VIPLPoint p4[4];
		pfld.detect(&m, &objs[i], p4);

        spdlog::debug("Elapsed for landmark {:.3}", sw);

		cv::Mat crop(cv::Size(100, 32), CV_8UC3);
		cv::Mat M(2, 3, CV_32FC1);
		transform2d_100x32(p4, (float *)M.data);
		cv::warpAffine(m, crop, M, crop.size());
		cv::imwrite("crop.jpg", crop);

        spdlog::debug("Elapsed for transform {:.3}", sw);

		PlateText_t result;
		crnn.detect(&crop, &result);

        spdlog::debug("Elapsed for plate {:.3}", sw);

		FILE *fp = fopen("result.txt", "a+");
		if (fp) {
			fprintf(fp, "%s\n", result.text);
			fclose(fp);
		}

		for(int k=0; k<4; k++) {
			cv::circle(m, cv::Point(p4[k].x, p4[k].y), 3, cv::Scalar(0, 0, 255), -1);
		}
	}

	for (int k=0; k<n; k++) {
		const VIPLObjectInfo& obj = objs[k];
		cv::Scalar c;
		if (obj.label == 0)
			c = cv::Scalar(0, 0, 255);
		else
			c = cv::Scalar(255, 0, 0);
		cv::rectangle(m, cv::Point(obj.x, obj.y), cv::Point(obj.x+obj.width, obj.y+obj.height), c);
	}

    spdlog::debug("Elapsed for plot {:.3}", sw);

	cv::imwrite("result.jpg", m);

    return true;
}

bool test_ncnn_plate2()
{
    spdlog::stopwatch sw;

	PlateCrnn crnn;
    cv::Mat m = cv::imread("./test/perspective100x32.jpg", cv::IMREAD_COLOR);
	if (m.empty()) {
		fprintf(stderr, "cv::imread failed\n");
		return false;
	}

    logger->debug("Elapsed for imread {:.3} s", sw);
    sw.reset();

    PlateText_t result;
    crnn.detect(&m, &result);

    logger->debug("Elapsed for plate {:.3} s", sw);
    sw.reset();

    std::string plate(result.text);

    LOG_DEBUG("plate_str=%s, plate_size=%d, plate_province=%s", plate.c_str(), plate.length(), plate.substr(0, 3).c_str());

    LOG_DEBUG("plate=%s, size=%d, score[0]=%f, score[last]=%f", result.text, result.size, result.score[0], result.score[result.size - 1]);

    FILE *fp = fopen("result.txt", "a+");
    if (fp) {
        fprintf(fp, "%s\n", result.text);
        fclose(fp);
    }

    return true;
}
*/

/*
int test_mnn_lpr()
{
    std::shared_ptr<DetectRecPlate> m_forwarder = CreateDrpApp();
    std::vector<cv::String> imgs_path;
    cv::glob("./test/plate/*", imgs_path);
    std::string plate_number = "";
    float plate_char_conf = 0.0;
    std::string plate_color = "";
    std::vector<float> char_conf;
    for (int j = 0; j < 100; j++) {
        for (int i = 0; i < imgs_path.size(); i++) {
            cv::Mat img = cv::imread(imgs_path[i]); // TODO 现在cv读不到png图片，只能读jpg
            m_forwarder->infer(img, plate_number, plate_char_conf, plate_color, char_conf);
            std::cout << imgs_path[i] << ": " << plate_number << " " << plate_char_conf << " " << plate_color << "\n\n";
        }
    }
    return HW_OK;
}
*/
